package com.league.blog.system.shiro.custom;

import com.league.blog.pojo.User;
import com.league.blog.system.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionException;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;


/**
 * @author 黄豪琦
 * 日期:2019-07-04 15:34
 * 说明:自定义并发登录拦截器
 */
@Slf4j
public class KicoutSessionControlFilter extends AccessControlFilter {


    /**
     * 被踢出后重定向的地址  后台接口路径
     */
    private String kicOutUrl = "/login?kickout=1";

    /**
     * 最大登录人数 默认为1
     */
    private int maxNum = 1;

    /**
     * 踢出前者还是后者 为true踢出后者 默认踢出前者
     */
    private boolean kicoutAfter = false;

    private SessionManager sessionManager;

    private RedisUtils redisUtils;

    private static final String DEFAULT_KICKOUT_CACHE_KEY_PREFIX = "shiro:cache:kickout:";

    private String keyPrefix = DEFAULT_KICKOUT_CACHE_KEY_PREFIX;

    private String getRedisKickoutKey(String username) {
        return this.keyPrefix + username;
    }

    private static final String KICOUT_PROPERTY_NAME = "kicout";

    /**
     * 是否允许访问，true表示允许
     * @param servletRequest
     * @param servletResponse
     * @param o
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    /**
     * 表示访问拒绝时是否自己处理，如果返回true表示自己不处理且继续拦截器链执行，返回false表示自己已经处理了（比如重定向到另一个页面）。
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        if(!subject.isAuthenticated() && !subject.isRemembered()){
            return true;
        }

        User user = (User)subject.getPrincipal();
        Session session = subject.getSession();

        Serializable sessionId = session.getId();

        //初始化用户队列 放进缓存
        Deque<Serializable> deque = (Deque<Serializable>)redisUtils.get(getRedisKickoutKey(user.getAccountName()));
        if(deque == null || deque.size() == 0){
            deque = new LinkedList<>();
        }

        //如果用户队列里没有此sessionId,且没有被踢出 则放入队列 并放入缓存
        if(!deque.contains(sessionId) && session.getAttribute(KICOUT_PROPERTY_NAME) == null){
            deque.push(sessionId);
            redisUtils.put(getRedisKickoutKey(user.getAccountName()), deque, -1);
        }

        //如果队列里的用户数量超过最大值 开始踢人
        while (deque.size() > maxNum){
            Serializable kicoutSessionId;
            //为true踢出前者  kicoutAfter默认是false
            if(!kicoutAfter){
                kicoutSessionId = deque.removeLast();
            } else {
                //否则踢出后者
                kicoutSessionId = deque.removeFirst();
            }
            try {
                Session kicoutSession = null;
                try {
                    kicoutSession = sessionManager.getSession(new DefaultSessionKey(kicoutSessionId));
                } catch (UnknownSessionException e) {
                    //缓存中的session不存在，说明此session已经过期，不再处理
                }
                if(kicoutSession != null){
                    //设置此属性为true表示这个会话被踢出了
                    kicoutSession.setAttribute(KICOUT_PROPERTY_NAME, true);
                    //更新redis
                    redisUtils.put(getRedisKickoutKey(user.getAccountName()), deque, -1);
                }

            } catch (SessionException e) {
                e.printStackTrace();
            }

        }

        if(session.getAttribute(KICOUT_PROPERTY_NAME) != null){
            //会话被踢出了
            try {
                subject.logout();
            } catch (Exception e) {
                log.error("踢出用户失败错误信息："+e.getMessage());
                e.printStackTrace();
            }
            WebUtils.issueRedirect(servletRequest, servletResponse, kicOutUrl);
            return false;

        }
        return true;
    }


    public void setKicOutUrl(String kicOutUrl) {
        this.kicOutUrl = kicOutUrl;
    }

    public void setMaxNum(int maxNum) {
        this.maxNum = maxNum;
    }

    public void setKicoutAfter(boolean kicoutAfter) {
        this.kicoutAfter = kicoutAfter;
    }

    @Autowired
    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    @Autowired
    public void setRedisUtils(RedisUtils redisUtils){
        this.redisUtils = redisUtils;
    }
}
